[chores] Added support for user expiration reminders #608#615
Conversation
| # TODO: Remove before merging | ||
| - "openwisp-radius[openvpn_status] @ https://github.com/openwisp/openwisp-radius/tarball/issues/706-user-expiration" | ||
| - "openwisp-users @ https://github.com/openwisp/openwisp-users/tarball/issues/499-user-expiration" |
There was a problem hiding this comment.
TODO: Remove before merging!
| - bpython | ||
| # TODO: Remove before merging | ||
| - "openwisp-radius[openvpn_status] @ https://github.com/openwisp/openwisp-radius/tarball/issues/706-user-expiration" | ||
| - "openwisp-users @ https://github.com/openwisp/openwisp-users/tarball/issues/499-user-expiration" |
There was a problem hiding this comment.
CRITICAL: TODO items must be resolved before merging. Temporary tarball dependencies should not be merged to master.
| "max_retries": {{ openwisp2_celery_broker_max_tries }}, | ||
| } | ||
|
|
||
| CELERY_TIMEZONE = TIME_ZONE |
There was a problem hiding this comment.
WARNING: The CELERY_TIMEZONE = TIME_ZONE setting is added here, but TIME_ZONE is defined later in the file (around line 420). While Python handles this at runtime, for clarity consider placing this after TIME_ZONE is defined, or add a comment noting it's a Django setting that will be resolved at runtime.
| "schedule": crontab(**{ {{ cron_password_expiration_email }} }), | ||
| }, | ||
| {% endif %} | ||
| "deactivate_expired_users": { |
There was a problem hiding this comment.
CRITICAL: The deactivate_expired_users task has been moved from openwisp_radius (conditional on openwisp2_radius and openwisp2_radius_periodic_tasks) to openwisp_users (always enabled). This changes behavior for non-RADIUS installations. Ensure this is intentional and that openwisp_users.tasks.deactivate_expired_users exists in all supported openwisp-users versions.
| "schedule": crontab(**{ {{ cron_deactivate_expired_users }} }), | ||
| "args": None, | ||
| }, | ||
| "expiration_reminder_email": { |
There was a problem hiding this comment.
CRITICAL: The expiration_reminder_email task runs unconditionally. Ensure openwisp_users.tasks.expiration_reminder_email exists in all supported openwisp-users versions, or wrap this in a conditional check similar to password_expiration_email above.
Code Review SummaryStatus: 2 Observations | Recommendation: Review task placement before merge Overview
Issue Details (click to expand)SUGGESTION
Resolved Issues (click to expand)FIXED
Other Observations (not in diff)
Files Reviewed (3 files)
Issue Alignment Check: This PR implements the changes described in #608 - adding celery beat schedules for expiration reminders and user deactivation tasks. Testing Recommendation: Since this PR modifies celery beat schedules and moves tasks between modules, ensure integration tests verify:
Reviewed by gpt-5.4-20260305 · 19,685 tokens |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
💤 Files with no reviewable changes (1)
📜 Recent review details⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
🔇 Additional comments (1)
📝 WalkthroughWalkthroughThis PR updates periodic scheduling for user account expiration: adds a new cron variable cron_expiration_reminder_email at 00:03, changes cron_deactivate_expired_users to 00:01, consolidates both tasks into the main CELERY_BEAT_SCHEDULE (removing the radius-scoped entry), and sets CELERY_TIMEZONE = TIME_ZONE. Sequence Diagram(s)sequenceDiagram
participant SettingsTemplate
participant CeleryBeat
participant openwisp_users.deactivate as openwisp_users.tasks.deactivate_expired_users
participant openwisp_users.reminder as openwisp_users.tasks.expiration_reminder_email
participant openwisp2_radius
SettingsTemplate->>CeleryBeat: register deactivate_expired_users (crontab)
SettingsTemplate->>CeleryBeat: register expiration_reminder_email (crontab)
CeleryBeat->>openwisp_users.deactivate: run at 00:01
CeleryBeat->>openwisp_users.reminder: run at 00:03
SettingsTemplate->>openwisp2_radius: remove radius-scoped deactivate task
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related issues
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (1/3). |
There was a problem hiding this comment.
♻️ Duplicate comments (2)
templates/openwisp2/settings.py (1)
248-248:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winFix import-time name resolution bug for
CELERY_TIMEZONE.Line 248 references
TIME_ZONEbefore it is assigned (Line 420), which can fail at settings import time.Proposed fix
-CELERY_TIMEZONE = TIME_ZONE +CELERY_TIMEZONE = "{{ openwisp2_time_zone }}"#!/bin/bash python - <<'PY' from pathlib import Path p = Path("templates/openwisp2/settings.py") lines = p.read_text().splitlines() celery_tz_line = next(i+1 for i, l in enumerate(lines) if l.strip().startswith("CELERY_TIMEZONE")) time_zone_line = next(i+1 for i, l in enumerate(lines) if l.strip().startswith("TIME_ZONE =")) print(f"CELERY_TIMEZONE line: {celery_tz_line}") print(f"TIME_ZONE line: {time_zone_line}") print("time_zone_defined_before_reference:", time_zone_line < celery_tz_line) PY🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@templates/openwisp2/settings.py` at line 248, CELERY_TIMEZONE is referencing TIME_ZONE before it's assigned; update settings so TIME_ZONE is defined prior to using it (move the CELERY_TIMEZONE assignment to after the TIME_ZONE = ... line) or change CELERY_TIMEZONE to read TIME_ZONE safely (e.g. via globals().get('TIME_ZONE', '<default>')) so CELERY_TIMEZONE and TIME_ZONE in templates/openwisp2/settings.py are resolved at import time; edit the CELERY_TIMEZONE assignment or its position and ensure the symbols CELERY_TIMEZONE and TIME_ZONE are consistently ordered or accessed safely.defaults/main.yml (1)
19-21:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winRemove temporary branch tarball dependencies from defaults before merge.
Line 19 explicitly marks this as temporary, and Line 20-Line 21 pin default installs to mutable GitHub issue branches, which is not safe/reproducible for
master.Proposed fix
openwisp2_extra_python_packages: - bpython - # TODO: Remove before merging - - "openwisp-radius[openvpn_status] @ https://github.com/openwisp/openwisp-radius/tarball/issues/706-user-expiration" - - "openwisp-users @ https://github.com/openwisp/openwisp-users/tarball/issues/499-user-expiration"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@defaults/main.yml` around lines 19 - 21, Remove the temporary GitHub tarball pins from defaults/main.yml: delete the entries containing "openwisp-radius[openvpn_status] @ https://github.com/openwisp/openwisp-radius/tarball/issues/706-user-expiration" and "openwisp-users @ https://github.com/openwisp/openwisp-users/tarball/issues/499-user-expiration" and replace them with stable, reproducible pins (either the package name alone or a released version spec, e.g., openwisp-radius==<released_version> and openwisp-users==<released_version>) so defaults no longer reference mutable issue-branch tarballs before merging.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@defaults/main.yml`:
- Around line 19-21: Remove the temporary GitHub tarball pins from
defaults/main.yml: delete the entries containing
"openwisp-radius[openvpn_status] @
https://github.com/openwisp/openwisp-radius/tarball/issues/706-user-expiration"
and "openwisp-users @
https://github.com/openwisp/openwisp-users/tarball/issues/499-user-expiration"
and replace them with stable, reproducible pins (either the package name alone
or a released version spec, e.g., openwisp-radius==<released_version> and
openwisp-users==<released_version>) so defaults no longer reference mutable
issue-branch tarballs before merging.
In `@templates/openwisp2/settings.py`:
- Line 248: CELERY_TIMEZONE is referencing TIME_ZONE before it's assigned;
update settings so TIME_ZONE is defined prior to using it (move the
CELERY_TIMEZONE assignment to after the TIME_ZONE = ... line) or change
CELERY_TIMEZONE to read TIME_ZONE safely (e.g. via globals().get('TIME_ZONE',
'<default>')) so CELERY_TIMEZONE and TIME_ZONE in
templates/openwisp2/settings.py are resolved at import time; edit the
CELERY_TIMEZONE assignment or its position and ensure the symbols
CELERY_TIMEZONE and TIME_ZONE are consistently ordered or accessed safely.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 0fb0316e-f344-4d78-bbfd-81729ba4734b
📒 Files selected for processing (3)
defaults/main.ymldocs/user/role-variables.rsttemplates/openwisp2/settings.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Build debian12
- GitHub Check: Build debian13
- GitHub Check: Build ubuntu2404
- GitHub Check: Build ubuntu2204
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{md,rst,txt}
📄 CodeRabbit inference engine (Custom checks)
**/*.{md,rst,txt}: Changes: If the change affects behavior that is already documented, the documentation must be updated to reflect the new behavior
Features: Documentation must mention the new feature; for heavily UI-related features, a new section or page is appropriate
Files:
docs/user/role-variables.rst
**/*.py
📄 CodeRabbit inference engine (Custom checks)
General: For Django pull requests, ensure all user-facing strings are marked as translatable using the Django i18n framework
Files:
templates/openwisp2/settings.py
🔇 Additional comments (3)
defaults/main.yml (1)
217-218: LGTM!docs/user/role-variables.rst (1)
450-451: LGTM!templates/openwisp2/settings.py (1)
256-264: LGTM!
|
The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (2/3). |
|
The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (3/3). |
nemesifier
left a comment
There was a problem hiding this comment.
Can you please send a separate PR to instruct the role to install the latest version of the modules? This will allow us to merge not only this PR but also others that are waiting for this.
f3e0020 to
1d8b00c
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@defaults/main.yml`:
- Around line 11-16: The current defaults define package sources using floating
tarball URLs (openwisp2_controller_version, openwisp2_network_topology_version,
openwisp2_firmware_upgrader_version, openwisp2_monitoring_version,
openwisp2_radius_version) which, combined with ansible.builtin.pip usage with
state: latest in tasks/pip.yml, makes installs non-reproducible; update each of
those variables to point to immutable refs (released version strings or tarball
URLs containing commit SHAs or release tags) instead of .../tarball/master so
repeated runs install the exact same artifact.
In `@templates/load_initial_data.py`:
- Around line 57-64: The current broad JSON text search and unconditional access
to template_obj.config["files"] is unsafe; tighten the predicate to search for
the JSON key/value pair (e.g. config_text__icontains='"path":
"/etc/dropbear/authorized_keys"') so you only match objects that declare that
path, and when iterating use template_obj.config.get("files", []) and check each
file dict for the presence and types of "path" and "contents" (e.g. ensure
file.get("path") == "/etc/dropbear/authorized_keys" and "contents" in file and
isinstance(file["contents"], str)) before reading or using those values to avoid
KeyError/TypeError in the code that processes files.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 0a12cbc8-c741-441a-b1bd-aba263ce1365
📒 Files selected for processing (4)
defaults/main.ymldocs/user/role-variables.rsttemplates/load_initial_data.pytemplates/openwisp2/settings.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: Build debian13
- GitHub Check: Build ubuntu2404
- GitHub Check: Build ubuntu2204
- GitHub Check: Build debian12
- GitHub Check: Kilo Code Review
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{md,rst,txt}
📄 CodeRabbit inference engine (Custom checks)
**/*.{md,rst,txt}: Changes: If the change affects behavior that is already documented, the documentation must be updated to reflect the new behavior
Features: Documentation must mention the new feature; for heavily UI-related features, a new section or page is appropriate
Files:
docs/user/role-variables.rst
**/*.py
📄 CodeRabbit inference engine (Custom checks)
General: For Django pull requests, ensure all user-facing strings are marked as translatable using the Django i18n framework
Files:
templates/load_initial_data.pytemplates/openwisp2/settings.py
🔇 Additional comments (4)
templates/load_initial_data.py (1)
19-20: LGTM!defaults/main.yml (1)
218-219: LGTM!docs/user/role-variables.rst (1)
450-451: LGTM!templates/openwisp2/settings.py (1)
255-263: LGTM!Also applies to: 420-420
| # TODO: Update when releasing OpenWISP 26 | ||
| openwisp2_controller_version: "openwisp-controller @ https://github.com/openwisp/openwisp-controller/tarball/master" | ||
| openwisp2_network_topology_version: "openwisp-network-topology @ https://github.com/openwisp/openwisp-network-topology/tarball/master" | ||
| openwisp2_firmware_upgrader_version: "openwisp-firmware-upgrader @ https://github.com/openwisp/openwisp-firmware-upgrader/tarball/master" | ||
| openwisp2_monitoring_version: "openwisp-monitoring @ https://github.com/openwisp/openwisp-monitoring/tarball/master" | ||
| openwisp2_radius_version: "openwisp-radius @ https://github.com/openwisp/openwisp-radius/tarball/master" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify floating refs in defaults and how they are installed.
rg -n 'tarball/(master|issues/)' defaults/main.yml
rg -n -C2 'Install extra python packages|openwisp2_extra_python_packages|state:\s*latest' tasks/pip.ymlRepository: openwisp/ansible-openwisp2
Length of output: 2503
Pin OpenWISP package sources to immutable refs.
defaults/main.yml lines 12-16 use floating direct URLs (.../tarball/master) for openwisp-controller, openwisp-network-topology, openwisp-firmware-upgrader, openwisp-monitoring, and openwisp-radius. These are then installed via ansible.builtin.pip with state: latest in tasks/pip.yml, so rerunning the role can pull different upstream contents from master, breaking reproducibility (and potentially behavior). Pin to released versions or immutable commit/archive URLs (e.g., commit SHA tarballs).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@defaults/main.yml` around lines 11 - 16, The current defaults define package
sources using floating tarball URLs (openwisp2_controller_version,
openwisp2_network_topology_version, openwisp2_firmware_upgrader_version,
openwisp2_monitoring_version, openwisp2_radius_version) which, combined with
ansible.builtin.pip usage with state: latest in tasks/pip.yml, makes installs
non-reproducible; update each of those variables to point to immutable refs
(released version strings or tarball URLs containing commit SHAs or release
tags) instead of .../tarball/master so repeated runs install the exact same
artifact.
| queryset = ( | ||
| Template.objects.annotate( | ||
| # Cast config to text because JSONField __contains lookup is not | ||
| # supported on all database backends (e.g. SQLite). | ||
| config_text=Cast("config", output_field=TextField()) | ||
| ) | ||
| .filter(config_text__icontains="/etc/dropbear/authorized_keys") | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate and print the relevant section with line numbers
FILE="templates/load_initial_data.py"
if [ ! -f "$FILE" ]; then
echo "Missing file: $FILE"
exit 1
fi
echo "== $FILE (lines 1-140) =="
nl -ba "$FILE" | sed -n '1,140p'
echo
echo "== $FILE (lines 140-260) =="
nl -ba "$FILE" | sed -n '140,260p'
echo
echo "== Search for config[\"files\"] usage =="
rg -n "config\[\s*\"files\"\s*\]" -S "$FILE" || true
echo
echo "== Search for template_obj.config usage =="
rg -n "template_obj\.config" -S "$FILE" || true
echo
echo "== Search for Template.objects.annotate(.filter(config_text) in repo =="
rg -n "config_text__icontains" -S . || trueRepository: openwisp/ansible-openwisp2
Length of output: 183
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="templates/load_initial_data.py"
if [ ! -f "$FILE" ]; then
echo "Missing file: $FILE"
exit 1
fi
echo "== $FILE (1-220) =="
cat -n "$FILE" | sed -n '1,220p'
echo
echo "== Search for config[\"files\"] usage =="
rg -n "config\[\s*\"files\"\s*\]" -S "$FILE" || true
echo
echo "== Search for template_obj.config usage =="
rg -n "template_obj\.config" -S "$FILE" || true
echo
echo "== Search for config_text__icontains usage =="
rg -n "config_text__icontains" -S "$FILE" || trueRepository: openwisp/ansible-openwisp2
Length of output: 4125
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Search for the Template model behind swapper load_model("config", "Template")
# by looking for "class Template" and "load_model("config", "Template")" and "config = models.JSONField"
rg -n "load_model\(\"config\", \"Template\"\)" -S . || true
rg -n "class Template\b" -S . || true
rg -n "Template.*models\.JSONField|models\.JSONField.*Template|config\s*=\s*models\.JSONField" -S . || true
echo
echo "== Find likely Template model files =="
# Use fd to list model files potentially containing Template
fd -e py -t f -g '*models*' . | head -n 50 || true
# Search within any likely config/template model files
fd -e py -t f -g '*config*' . | head -n 200 | xargs -I{} sh -c 'rg -n "class Template\b" "{}" || true' || trueRepository: openwisp/ansible-openwisp2
Length of output: 190
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Search for authorized_keys path occurrences =="
rg -n "/etc/dropbear/authorized_keys" -S . || true
echo
echo "== Search for config schema examples containing \"files\" =="
rg -n "\"files\"" -S . || true
echo
echo "== Search for template config mutation/access patterns =="
rg -n "config\[\s*\"files\"\s*\]|config\.get\(\s*\"files\"" -S . || trueRepository: openwisp/ansible-openwisp2
Length of output: 792
Avoid broad text matching for JSON config; guard template_obj.config["files"] access
config_text__icontains="/etc/dropbear/authorized_keys" can match unrelated JSON content, but later code unconditionally does template_obj.config["files"] (KeyError risk). Add defensive access (iterate over template_obj.config.get("files", []), and guard path/contents) and tighten the text predicate to look for the "path" key/value pair rather than only the raw authorized_keys string.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@templates/load_initial_data.py` around lines 57 - 64, The current broad JSON
text search and unconditional access to template_obj.config["files"] is unsafe;
tighten the predicate to search for the JSON key/value pair (e.g.
config_text__icontains='"path": "/etc/dropbear/authorized_keys"') so you only
match objects that declare that path, and when iterating use
template_obj.config.get("files", []) and check each file dict for the presence
and types of "path" and "contents" (e.g. ensure file.get("path") ==
"/etc/dropbear/authorized_keys" and "contents" in file and
isinstance(file["contents"], str)) before reading or using those values to avoid
KeyError/TypeError in the code that processes files.
|
The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (1/3). |
|
The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (2/3). |
|
The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (1/3). |
|
The CI is failing due to transient infrastructure issues (not related to your code). I have restarted the failed jobs automatically (2/3). |
| - "openwisp-radius[openvpn_status] @ https://github.com/openwisp/openwisp-radius/tarball/issues/706-user-expiration" | ||
| - "openwisp-users @ https://github.com/openwisp/openwisp-users/tarball/issues/499-user-expiration" |
There was a problem hiding this comment.
| - "openwisp-radius[openvpn_status] @ https://github.com/openwisp/openwisp-radius/tarball/issues/706-user-expiration" | |
| - "openwisp-users @ https://github.com/openwisp/openwisp-users/tarball/issues/499-user-expiration" |
- Added celery beat schedule for expiration reminder emails - Added support for openwisp_users expiration tasks - Added configurable reminder email schedule variable Closes #608
0163d6d to
8204f0c
Compare
|
@coderabbitai full review! |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Read all changed files
echo "=== defaults/main.yml ==="
cat defaults/main.yml
echo ""
echo "=== templates/openwisp2/settings.py ==="
cat templates/openwisp2/settings.py
echo ""
echo "=== docs/user/role-variables.rst ==="
cat docs/user/role-variables.rst
echo ""
echo "=== PR diff ==="
gh pr diff 615 --repo openwisp/ansible-openwisp2Length of output: 60219
Overall AssessmentThe PR correctly moves A few issues worth addressing: Issue 1 —
|
|
Actionable comments posted: 0 |
Checklist
Reference to Existing Issue
Closes #608
Description of Changes
Blockers